/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.config.console;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.tianyun.cloud.config.console.client.ConsoleConfigServer;
import org.tianyun.cloud.config.console.client.response.ComponentResponse;
import org.tianyun.cloud.config.console.client.response.ConfigResponse;
import org.tianyun.cloud.config.console.client.response.NamespaceResponse;
import org.tianyun.cloud.config.console.configuration.ConsoleConfigProperties;
import org.tianyun.cloud.config.console.property.Namespace;
import org.tianyun.cloud.config.console.sources.ConsolePropertySource;
import org.tianyun.cloud.config.console.sources.NameWildcardPropertySource;
import org.tianyun.cloud.config.console.sources.WholePropertySource;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;

/**
 * 控制台属性源加载
 *
 * @author ebert_chan
 */
public class ConsolePropertySourceLocator implements PropertySourceLocator, PriorityOrdered {

    private static final Logger log = LoggerFactory.getLogger(ConsolePropertySourceLocator.class);

    public static final String MIXTURE_PROPERTY_SOURCE_NAME = "mixture";

    public static final String CONSOLE_PROPERTY_SOURCE_NAME = "console";

    public static final String WHOLE_PROPERTY_SOURCE_NAME = "whole";

    public static final String PUBLIC_NAMESPACE_NAME = "public";

    @Override
    public PropertySource<?> locate(Environment environment) {
        String applicationName = environment.getProperty("spring.application.name");
        if (!StringUtils.hasText(applicationName)) {
            throw new RuntimeException("The spring.application.name configuration must be specified.");
        }

        ConsoleConfigProperties consoleConfigProperties = new ConsoleConfigProperties();
        Binder.get(environment).bind("spring.cloud.console.config", Bindable.ofInstance(consoleConfigProperties));

        if (!StringUtils.hasText(consoleConfigProperties.getServerAddr())) {
            throw new RuntimeException("The spring.cloud.console.config.server-addr configuration must be specified.");
        }

        ConsoleConfigServer.init(consoleConfigProperties);

        ConfigResponse config = ConsoleConfigServer.getConfig(applicationName);

        CompositePropertySource propertySource = load(config);

        return propertySource;
    }

    private CompositePropertySource load(ConfigResponse config) {
        CompositePropertySource compositePropertySource = new CompositePropertySource(MIXTURE_PROPERTY_SOURCE_NAME);
        WholePropertySource wholePropertySource = new WholePropertySource(WHOLE_PROPERTY_SOURCE_NAME);
        ConsolePropertySource consolePropertySource = new ConsolePropertySource(CONSOLE_PROPERTY_SOURCE_NAME);

        compositePropertySource.addPropertySource(wholePropertySource);
        compositePropertySource.addPropertySource(consolePropertySource);

        if (config != null) {
            MutablePropertySources mutablePropertySources = new MutablePropertySources();
            mutablePropertySources.addLast(wholePropertySource);
            mutablePropertySources.addLast(consolePropertySource);

            updatePropertySource(mutablePropertySources, config);
        }


        return compositePropertySource;
    }

    public void updatePropertySource(MutablePropertySources mutablePropertySources, ConfigResponse config) {
        if (config == null || CollectionUtils.isEmpty(config.getNamespaces())) {
            return;
        }

        WholePropertySource wholePropertySource = (WholePropertySource) mutablePropertySources.get(WHOLE_PROPERTY_SOURCE_NAME);
        ConsolePropertySource consolePropertySource = (ConsolePropertySource) mutablePropertySources.get(CONSOLE_PROPERTY_SOURCE_NAME);

        for (NamespaceResponse namespaceResponse : config.getNamespaces()) {
            if (CollectionUtils.isEmpty(namespaceResponse.getComponents())) {
                continue;
            }

            // 控制台公共配置
            if (PUBLIC_NAMESPACE_NAME.equals(namespaceResponse.getNamespace())) {
                updateWholePropertySource(wholePropertySource, namespaceResponse);
                continue;
            }

            updateConsolePropertySource(consolePropertySource, namespaceResponse);
        }
    }

    private void updateWholePropertySource(WholePropertySource wholePropertySource, NamespaceResponse namespaceResponse) {
        if (wholePropertySource == null) {
            return;
        }
        try {
            Properties summary = new Properties();
            for (ComponentResponse componentResponse : namespaceResponse.getComponents()) {
                Properties prop = PropertiesLoaderUtils.loadProperties(new ByteArrayResource(componentResponse.getProperties().getBytes(StandardCharsets.UTF_8)));
                summary.putAll(prop);
            }
            wholePropertySource.load(summary);
            log.info("Successfully loaded configuration, namespance={}", namespaceResponse.getNamespace());
        } catch (IOException e) {
            log.error("Configuration parsing failed, ignored. namespance={}", namespaceResponse.getNamespace());
        }
    }

    private void updateConsolePropertySource(ConsolePropertySource consolePropertySource, NamespaceResponse namespaceResponse) {
        if (consolePropertySource == null) {
            return;
        }
        // 控制台各组件独立配置
        Namespace namespace = new Namespace(namespaceResponse.getNamespace());
        for (ComponentResponse componentResponse : namespaceResponse.getComponents()) {
            try {
                Properties prop = PropertiesLoaderUtils.loadProperties(new ByteArrayResource(componentResponse.getProperties().getBytes(StandardCharsets.UTF_8)));
                NameWildcardPropertySource nameWildcardPropertySource = new NameWildcardPropertySource(componentResponse.getName(), prop);
                namespace.putPropertySource(nameWildcardPropertySource);
                log.info("Successfully loaded console configuration, namespance={}, component={}", namespaceResponse.getNamespace(), componentResponse.getName());
            } catch (IOException e) {
                log.error("Configuration parsing failed, ignored. namespance={}, component={}", namespaceResponse.getNamespace(), componentResponse.getName());
            }
        }
        consolePropertySource.putNamespace(namespace);
    }


    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

}
